package jfconfig;

/**
 * Created : Mar 16, 2012
 *
 * @author pquiring
 */

import java.io.*;
import java.util.Arrays;
import javax.swing.*;

import javaforce.*;
import javaforce.linux.*;

public class ProxyPanel extends javax.swing.JPanel {

  /**
   * Creates new form ProxyPanel
   */
  public ProxyPanel() {
    initComponents();
    loadRules();
    updateRules();
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    jToolBar1 = new javax.swing.JToolBar();
    back = new javax.swing.JButton();
    addRule = new javax.swing.JButton();
    editRule = new javax.swing.JButton();
    delRule = new javax.swing.JButton();
    save = new javax.swing.JButton();
    apply = new javax.swing.JButton();
    restart = new javax.swing.JButton();
    jScrollPane1 = new javax.swing.JScrollPane();
    rules = new javax.swing.JList();
    forwardedFor = new javax.swing.JCheckBox();
    jLabel1 = new javax.swing.JLabel();
    port = new javax.swing.JTextField();
    jLabel2 = new javax.swing.JLabel();

    jToolBar1.setFloatable(false);
    jToolBar1.setRollover(true);

    back.setText("<Back");
    back.setFocusable(false);
    back.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    back.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    back.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        backActionPerformed(evt);
      }
    });
    jToolBar1.add(back);

    addRule.setText("Add Rule");
    addRule.setFocusable(false);
    addRule.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    addRule.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    addRule.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        addRuleActionPerformed(evt);
      }
    });
    jToolBar1.add(addRule);

    editRule.setText("Edit Rule");
    editRule.setFocusable(false);
    editRule.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    editRule.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    editRule.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        editRuleActionPerformed(evt);
      }
    });
    jToolBar1.add(editRule);

    delRule.setText("Del Rule");
    delRule.setFocusable(false);
    delRule.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    delRule.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    delRule.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        delRuleActionPerformed(evt);
      }
    });
    jToolBar1.add(delRule);

    save.setText("Save");
    save.setFocusable(false);
    save.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    save.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    save.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        saveActionPerformed(evt);
      }
    });
    jToolBar1.add(save);

    apply.setText("Apply");
    apply.setFocusable(false);
    apply.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    apply.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    apply.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        applyActionPerformed(evt);
      }
    });
    jToolBar1.add(apply);

    restart.setText("Restart Server");
    restart.setFocusable(false);
    restart.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    restart.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    restart.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        restartActionPerformed(evt);
      }
    });
    jToolBar1.add(restart);

    rules.setModel(rulesModel);
    jScrollPane1.setViewportView(rules);

    forwardedFor.setSelected(true);
    forwardedFor.setText("Enable \"Forwarded-For\"");

    jLabel1.setText("Port");

    port.setText("3128");

    jLabel2.setText("Client Rules:");

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
      .addComponent(jScrollPane1)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
          .addGroup(layout.createSequentialGroup()
            .addComponent(jLabel1)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(port))
          .addComponent(forwardedFor)
          .addComponent(jLabel2))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel1)
          .addComponent(port, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(forwardedFor)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabel2)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE))
    );
  }// </editor-fold>//GEN-END:initComponents

  private void backActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backActionPerformed
    ConfigApp.This.setPanel(new ServersPanel());
  }//GEN-LAST:event_backActionPerformed

  private void addRuleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addRuleActionPerformed
    addRule();
  }//GEN-LAST:event_addRuleActionPerformed

  private void editRuleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleActionPerformed
    int idx = rules.getSelectedIndex();
    if (idx == -1) return;
    editRule(idx);
  }//GEN-LAST:event_editRuleActionPerformed

  private void delRuleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_delRuleActionPerformed
    int idx = rules.getSelectedIndex();
    if (idx == -1) return;
    delRule(idx);
  }//GEN-LAST:event_delRuleActionPerformed

  private void saveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveActionPerformed
    saveRules();
  }//GEN-LAST:event_saveActionPerformed

  private void applyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyActionPerformed
    apply();
  }//GEN-LAST:event_applyActionPerformed

  private void restartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_restartActionPerformed
    restart();
  }//GEN-LAST:event_restartActionPerformed

  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton addRule;
  private javax.swing.JButton apply;
  private javax.swing.JButton back;
  private javax.swing.JButton delRule;
  private javax.swing.JButton editRule;
  private javax.swing.JCheckBox forwardedFor;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JToolBar jToolBar1;
  private javax.swing.JTextField port;
  private javax.swing.JButton restart;
  private javax.swing.JList rules;
  private javax.swing.JButton save;
  // End of variables declaration//GEN-END:variables

/*
Support Options:
forwarded_for on|off
http_port 3128
acl <src_name> src <ip/mask>
acl <url_name> url_regex <url>
http_access allow [<url_name>] <src_name>
*/

  private DefaultListModel rulesModel = new DefaultListModel();

  public static class Rule {
    public Rule() {};
    public boolean enabled;
    public String ip;
    public String urls;  //comma seperated ("all" = no limit)
  }

  public static class Config {
    public Rule rule[];
    public boolean forwardedFor;
  }

  private Config config;
  private String configFolder = "/etc/jconfig.d/";
  private String configFile = "proxy.xml";

  private void loadRules() {
    config = new Config();
    config.rule = new Rule[0];
    try {
      XML xml = new XML();
      FileInputStream fis = new FileInputStream(configFolder + configFile);
      xml.read(fis);
      xml.writeClass(config);
    } catch (FileNotFoundException e1) {
      defaultConfig();
    } catch (Exception e2) {
      JFLog.log(e2);
      defaultConfig();
    }
    forwardedFor.setSelected(config.forwardedFor);
  }

  private void defaultConfig() {
    config = new Config();
    config.forwardedFor = true;
    config.rule = new Rule[0];
  }

  private void saveRules() {
    config.forwardedFor = forwardedFor.isSelected();
    try {
      XML xml = new XML();
      File tmpFile = File.createTempFile("proxy", ".xml");
      FileOutputStream fos = new FileOutputStream(tmpFile);
      xml.readClass("proxy", config);
      xml.write(fos);
      fos.close();
      Linux.mkdir(configFolder);
      if (!Linux.copyFile(tmpFile.getAbsolutePath(), configFolder + configFile)) {
        tmpFile.delete();
        throw new Exception("file io error");
      }
      tmpFile.delete();
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private String getRuleString(Rule rule) {
    return rule.ip + ":" + rule.urls;
  }

  private void updateRules() {
    rulesModel.clear();
    for(int a=0;a<config.rule.length;a++) {
      rulesModel.addElement(getRuleString(config.rule[a]));
    }
    forwardedFor.setSelected(config.forwardedFor);
  }

  private void up(int idx) {
    if (idx == 0) return;
    Rule tmp = config.rule[idx-1];
    config.rule[idx-1] = config.rule[idx];
    config.rule[idx] = tmp;
    updateRules();
    rules.setSelectedIndex(idx-1);
  }

  private void down(int idx) {
    if (idx == config.rule.length-1) return;
    Rule tmp = config.rule[idx+1];
    config.rule[idx+1] = config.rule[idx];
    config.rule[idx] = tmp;
    updateRules();
    rules.setSelectedIndex(idx+1);
  }

  private void addRule() {
    ProxyRuleDialog dialog = new ProxyRuleDialog(ConfigApp.This, true, null);
    dialog.setVisible(true);
    if (!dialog.accepted) return;
    Rule newRule = new Rule();
    newRule.enabled = dialog.getEnabled();
    newRule.ip = dialog.getIP();
    newRule.urls = dialog.getURLs().replaceAll("\n", ",");
    config.rule = Arrays.copyOf(config.rule, config.rule.length + 1);
    config.rule[config.rule.length-1] = newRule;
    updateRules();
  }

  private void editRule(int idx) {
    Rule rule = config.rule[idx];
    ProxyRuleDialog dialog = new ProxyRuleDialog(ConfigApp.This, true, rule);
    dialog.setVisible(true);
    if (!dialog.accepted) return;
    rule.enabled = dialog.getEnabled();
    rule.ip = dialog.getIP();
    rule.urls = dialog.getURLs().replaceAll("\n", ",");
    updateRules();
  }

  private void delRule(int idx) {
    Rule rule = config.rule[idx];
    if (!JFAWT.showConfirm("Confirm", "Are you sure you want to delete '" + rule.ip + "'?")) return;
    int len = config.rule.length;
    Rule newList[] = new Rule[len-1];
    System.arraycopy(config.rule, 0, newList, 0, idx);
    System.arraycopy(config.rule, idx+1, newList, idx, len - idx - 1);
    config.rule = newList;
    updateRules();
  }

//acl <src_name> src <ip/mask>
//acl <url_name> url_regex <url>
//http_access allow [<url_name>] <src_name>

  private void applyRules(OutputStream os) throws Exception {
    os.write("#JCONFIG-START:DO NOT EDIT THESE LINES-AUTO GENERATED FROM JCONFIG\n".getBytes());
    for(int a=0;a<config.rule.length;a++) {
      Rule rule = config.rule[a];
      if (!rule.enabled) continue;
      os.write(("acl rule_ip_" + a + " src " + rule.ip + "\n").getBytes());
      if (rule.urls.equals("all")) {
        os.write(("http_access allow rule_ip_" + a + "\n").getBytes());
      } else {
        String lns[] = rule.urls.split(",");
        for(int b=0;b<lns.length;b++) {
          String regex = lns[b].replaceAll("[.]", "[.]");  //looks strange but replaces . with [.]
          os.write(("acl rule_url_" + a + "_" + b + " url_regex " + regex + "\n").getBytes());
          os.write(("http_access allow rule_url_" + a + "_" + b + " rule_ip_" + a + "\n").getBytes());
        }
      }
    }
    os.write("#JCONFIG-STOP:DO NOT EDIT THESE LINES-AUTO GENERATED FROM JCONFIG\n".getBytes());
  }

  private void apply() {
    try {
      //copy /etc/squid3/squid.conf to tmpFile changing settings on the fly
      File tmpFile = File.createTempFile("proxy", ".xml");
      FileOutputStream fos = new FileOutputStream(tmpFile);
      FileInputStream fis = new FileInputStream("/etc/squid3/squid.conf");
      BufferedReader br = new BufferedReader(new InputStreamReader(fis));
      String ln;
      boolean inRules = false, insertRules = false, rulesApplied = false;
      while ((ln = br.readLine()) != null) {
        if (inRules) {
          if (ln.startsWith("#JCONFIG-STOP")) inRules = false;
          continue;
        } else {
          if (ln.startsWith("#JCONFIG-START")) {inRules = true; continue;}
          else if ((!rulesApplied) && (ln.indexOf("INSERT YOUR OWN RULE") != -1)) {
            insertRules = true;
          }
          else if ((ln.startsWith("forwarded_for ")) || (ln.startsWith("# forwarded_for "))) {
            if (config.forwardedFor) {
              fos.write("forwarded_for on\n".getBytes());
            } else {
              fos.write("forwarded_for off\n".getBytes());
            }
            continue;
          }
        }
        fos.write(ln.getBytes());
        fos.write("\n".getBytes());
        if (insertRules) {
          applyRules(fos);
          insertRules = false;
          rulesApplied = true;
        }
      }
      fos.close();
      fis.close();
      if (inRules) {
        tmpFile.delete();
        JFAWT.showError("Error", "Squid config is corrupt, can not apply settings");
        return;
      }
      //copy tmpFile back to /etc/squid3/squid.conf
      if (!Linux.copyFile(tmpFile.getAbsolutePath(), "/etc/squid3/squid.conf"))
        throw new Exception("file copy error");
      tmpFile.delete();
      JFAWT.showMessage("Notice", "Settings have been applied, please restart server.");
    } catch (Exception e) {
      JFLog.log(e);
      JFAWT.showError("Error", "Failed to apply settings.");
    }
  }

  private void restart() {
    if (Linux.restartService("squid3"))
      JFAWT.showMessage("Notice", "Squid Service Restarted");
    else
      JFAWT.showError("Error", "Failed to Restart Squid Service");
  }

}
